﻿using ArtMath.Resource;
using System;
using System.Collections.Generic;
using System.Text;

namespace ArtMath.Core.Helper
{
    /// <summary>
    /// 导函数
    /// </summary>
    public class Derivation
    {
        Dictionary<string, string> dTable = new Dictionary<string, string>();
        string func, indepVar;
        public Derivation(string functionExpression, string independentVariable)
        {
            func = functionExpression;
            indepVar = independentVariable;
        }
        public string GetDerivation()
        {
            if (string.IsNullOrEmpty(func)) return func;//throw new ArgumentException(Resources.Translate("EmptyExpression"));
            dTable.Clear();
            buildTable(func);
            if (baseDeri(func, out string tmp))
            {
                tmp = Utility.StandardizePolynomial(tmp);
                if (Calculation.Calculate(tmp, out double num))
                {
                    return num.ToString();
                }
            }
            return tmp;
        }
        bool baseDeri(string exp, out string result)
        {
            if (dTable.TryGetValue(exp, out result)) return true;
            StringBuilder sb = new StringBuilder(exp);
            bool hasOp = false;
            if (sb[0] == '-')
            {
                hasOp = true;
                sb.Insert(0, '0');
            }
            sb.Append('#');
            Queue<string> q = new Queue<string>();
            int i = 0, bracket = 0;
            for (int j = 0; j < sb.Length; j++)
            {
                if (Calculation.IsOperator(sb[j].ToString()))
                {
                    if (!hasOp && sb[j] != '#')
                    {
                        hasOp = true;
                    }
                    switch (sb[j])
                    {
                        case '(':
                            if (j > 0 && char.IsLetter(sb[j - 1]))
                            {
                                bracket++;
                            }
                            break;
                        case ')':
                            if (bracket > 0)
                            {
                                bracket--;
                                if (bracket == 0)
                                {
                                    continue;
                                }
                            }
                            break;
                    }
                    if (bracket > 0)
                    {
                        continue;
                    }
                    if (i == j)
                    {
                        if (!Utility.IsSign(sb, j))//正负号不添加
                        {
                            q.Enqueue(sb[j].ToString());
                            i++;
                        }
                    }
                    else
                    {
                        q.Enqueue(sb.ToString(i, j - i));
                        q.Enqueue(sb[j].ToString());
                        i = j + 1;
                    }
                }
            }
            if (!hasOp)
            {
                result = exp == indepVar ? "1" : "0";
                return true;
            }
            //中序转后序
            List<string> posterOrder = new List<string>();
            Stack<string> inOrder = new Stack<string>();
            inOrder.Push("#");
            int count = q.Count;
            for (i = 0; i < count; i++)
            {
                string item = q.Dequeue();
                if (Calculation.IsOperator(item))
                {
                    char chr;
                    while ((chr = Calculation.IsPriority(inOrder.Peek(), item)) == '>')
                    {
                        string temp = inOrder.Pop();
                        if (temp != "(" && temp != ")")
                        {
                            posterOrder.Add(temp);
                        }
                    }
                    switch (chr)
                    {
                        case '=':
                            inOrder.Pop();
                            break;
                        case '?':
                            result = null;
                            return false;
                            //throw new ArithmeticException(Resources.Translate("DerivationFailed"));
                        default:
                            inOrder.Push(item);
                            break;
                    }
                }
                else
                {
                    posterOrder.Add(item);
                }
            }
            if (inOrder.Count > 0)
            {
                result = null;
                return false;
                //throw new ArithmeticException(Resources.Translate("DerivationFailed"));
            }
            posterOrder.Add("#");
            for (i = 0; i < posterOrder.Count && posterOrder[i] != "#"; i++)
            {
                if (Calculation.IsOperator(posterOrder[i]))
                {
                    string deri = null, lderi, rderi;
                    if (dTable.ContainsKey(posterOrder[i - 2]))
                    {
                        lderi = dTable[posterOrder[i - 2]];
                    }
                    else
                    {
                        if (!baseDeri(posterOrder[i - 2], out lderi))
                        {
                            return false;
                        }
                        dTable.Add(posterOrder[i - 2], lderi);
                    }
                    if (dTable.ContainsKey(posterOrder[i - 1]))
                    {
                        rderi = dTable[posterOrder[i - 1]];
                    }
                    else
                    {
                        if (!baseDeri(posterOrder[i - 1], out rderi))
                        {
                            return false;
                        }
                        dTable.Add(posterOrder[i - 1], rderi);
                    }
                    double? lval, rval;
                    if (Calculation.Calculate(posterOrder[i - 2], out double lp))
                    {
                        lval = lp;
                    }
                    else
                    {
                        lval = null;
                    }
                    if (Calculation.Calculate(posterOrder[i - 1], out double rp))
                    {
                        rval = rp;
                    }
                    else
                    {
                        rval = null;
                    }
                    switch (posterOrder[i])
                    {
                        case "+":
                            if (lval.HasValue)
                            {
                                if (rval.HasValue)
                                {
                                    deri = "0";
                                }
                                else
                                {
                                    deri = rderi;
                                }
                            }
                            else
                            {
                                if (rval.HasValue)
                                {
                                    deri = lderi;
                                }
                                else
                                {
                                    deri = $"({lderi}+{rderi})";
                                }
                            }
                            break;
                        case "-":
                            if (lval.HasValue)
                            {
                                if (rval.HasValue)
                                {
                                    deri = "0";
                                }
                                else
                                {
                                    deri = $"(-{rderi})";
                                }
                            }
                            else
                            {
                                if (rval.HasValue)
                                {
                                    deri = lderi;
                                }
                                else
                                {
                                    deri = $"({lderi}-{rderi})";
                                }
                            }
                            break;
                        case "*":
                            if (lval.HasValue)
                            {
                                if (rval.HasValue)
                                {
                                    deri = "0";
                                }
                                else
                                {
                                    deri = $"{lval.Value}*{rderi}";
                                }
                            }
                            else
                            {
                                if (rval.HasValue)
                                {
                                    deri = $"{rval.Value}*{lderi}";
                                }
                                else
                                {
                                    deri = $"({lderi}*({posterOrder[i - 1]})+({posterOrder[i - 2]})*{rderi})";
                                }
                            }
                            break;
                        case "/":
                            if (lval.HasValue)
                            {
                                if (rval.HasValue)
                                {
                                    deri = "0";
                                }
                                else
                                {
                                    deri = $"-{lval.Value}*{rderi}/({posterOrder[i - 1]})^2";
                                }
                            }
                            else
                            {
                                if (rval.HasValue)
                                {
                                    deri = $"{lderi}/{rval.Value}";
                                }
                                else
                                {
                                    deri = $"({lderi}*({posterOrder[i - 1]})-({posterOrder[i - 2]})*{rderi})/({posterOrder[i - 1]})^2";
                                }
                            }
                            break;
                        case "^":
                            if (lval.HasValue)
                            {
                                if (rval.HasValue)
                                {
                                    deri = "0";
                                }
                                else
                                {
                                    deri = $"({posterOrder[i - 2]})^({posterOrder[i - 1]})*{rderi}*ln({posterOrder[i - 2]})";
                                }
                            }
                            else
                            {
                                if (rval.HasValue)
                                {
                                    deri = $"{rval.Value}*{lderi}*({posterOrder[i - 2]})^{rval.Value - 1}";
                                }
                                else
                                {
                                    deri = $"({posterOrder[i - 2]})^({posterOrder[i - 1]})*({rderi}*ln({posterOrder[i - 2]})+({posterOrder[i - 1]})*{lderi}/({posterOrder[i - 2]}))";
                                }
                            }
                            break;
                    }
                    if (deri != null)
                    {
                        posterOrder[i - 2] = $"({posterOrder[i - 2]}){posterOrder[i]}({posterOrder[i - 1]})";
                        if (!dTable.ContainsKey(posterOrder[i - 2]))
                        {
                            dTable.Add(posterOrder[i - 2], deri);
                        }
                    }
                    posterOrder.RemoveAt(i);
                    posterOrder.RemoveAt(i - 1);
                    i -= 2;
                }
            }
            if (dTable.TryGetValue(posterOrder[0], out result)) return true;
            result = null;
            return false;
        }
        void buildTable(string exp)
        {
            Stack<KeyValuePair<string, int>> func = new Stack<KeyValuePair<string, int>>();
            StringBuilder funcName = new StringBuilder();
            for (int i = 0; i < exp.Length; i++)
            {
                if (char.IsLetter(exp[i]))
                {
                    funcName.Append(exp[i]);
                }
                else
                {
                    switch (exp[i])
                    {
                        case '+':
                        case '-':
                        case '*':
                        case '/':
                        case '^':
                            funcName.Clear();
                            break;
                        case '(':
                            func.Push(new KeyValuePair<string, int>(funcName.ToString(), i));
                            funcName.Clear();
                            break;
                        case ')':
                            KeyValuePair<string, int> lbrk = func.Pop();
                            string yhs = exp.Substring(lbrk.Value - lbrk.Key.Length, i - lbrk.Value + lbrk.Key.Length + 1);
                            if (string.IsNullOrEmpty(lbrk.Key) || dTable.ContainsKey(yhs)) continue;
                            string[] exps = exp.Substring(lbrk.Value + 1, i - lbrk.Value - 1).Split(',');
                            KeyValuePair<string, string>[] args = new KeyValuePair<string, string>[exps.Length];
                            for (int j = 0; j < exps.Length; j++)
                            {
                                int expLastIndex = exps[j].Length - 1;
                                //bool degreeFlag = false;
                                if ((lbrk.Key.Equals("sin", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("cos", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("tan", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("sinh", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("cosh", StringComparison.OrdinalIgnoreCase) || lbrk.Key.Equals("tanh", StringComparison.OrdinalIgnoreCase)) &&
                                    (exps[j][expLastIndex] == 'd' || exps[j][expLastIndex] == 'D') &&
                                    (char.IsDigit(exps[j][expLastIndex - 1]) || exps[j][expLastIndex - 1] == ')' || exps[j][expLastIndex - 1] == '%'))
                                {
                                    //degreeFlag = true;
                                    expLastIndex--;
                                }
                                bool isPercentage = exp[expLastIndex] == '%';
                                if (isPercentage) expLastIndex--;
                                if (!baseDeri(exps[j].Substring(0, expLastIndex + 1), out string tmp))
                                {
                                    return;
                                }
                                args[j] = new KeyValuePair<string, string>(exps[j], tmp);
                                //if (isPercentage) args[j] /= 100;
                                //if (degreeFlag) args[j] *= Angle.DToR;
                            }
                            string deri = funcDeri(lbrk.Key, args);
                            dTable.Add(yhs, deri);
                            break;
                    }
                }
            }
        }
        static string funcDeri(string funcName, params KeyValuePair<string, string>[] args)
        {
            if (string.IsNullOrEmpty(funcName))
            {
                return args[0].Value;
            }
            switch (funcName)
            {
                case "sqrt":
                    return $"{args[0].Value}/(2*sqrt({args[0].Key}))";
                case "cbrt":
                    return $"{args[0].Value}*({args[0].Key})^(-2/3)/3";
                case "rt":
                    return $"({args[1].Key})^(1/({args[0].Key}))*(-ln({args[1].Key})/({args[0].Key})^2+{args[1].Value}/(({args[0].Key})*({args[1].Key})))";
                case "ln":
                    return $"{args[0].Value}/({args[0].Key})";
                case "lg":
                    return $"{args[0].Value}/(ln(10)*({args[0].Key}))";
                case "log":
                    return $"(({args[1].Value}*ln({args[0].Key}))/({args[1].Key})-({args[0].Value}*ln({args[1].Key}))/({args[0].Key}))/ln({args[0].Key})^2";
                case "sin":
                    return $"{args[0].Value}*cos({args[0].Key})";
                case "sinh":
                    return $"{args[0].Value}*cosh({args[0].Key})";
                case "cos":
                    return $"-{args[0].Value}*sin({args[0].Key})";
                case "cosh":
                    return $"{args[0].Value}*sinh({args[0].Key})";
                case "tan":
                case "tg":
                    return $"{args[0].Value}*sec({args[0].Key})^2";
                case "tanh":
                case "tgh":
                    return $"{args[0].Value}*(1-tanh({args[0].Key})^2)";
                case "cot":
                case "ctg":
                    return $"-{args[0].Value}*csc({args[0].Key})^2";
                case "sec":
                    return $"{args[0].Value}*sec({args[0].Key})*tan({args[0].Key})";
                case "csc":
                    return $"-{args[0].Value}*csc({args[0].Key})*cot({args[0].Key})";
                case "arcsin":
                case "asin":
                    return $"{args[0].Value}/sqrt(1-({args[0].Key})^2)";
                case "arccos":
                case "acos":
                    return $"-{args[0].Value}/sqrt(1-({args[0].Key})^2)";
                case "arctan":
                case "atan":
                    return $"{args[0].Value}/(1+({args[0].Key})^2)";
                case "arccot":
                case "arcctg":
                case "acot":
                case "actg":
                    return $"-{args[0].Value}/(1+({args[0].Key})^2)";
                default:
                    throw new ArithmeticException(string.Format(Resources.Translate("NotSupportDerivation"), funcName));
            }
        }
    }
}
